package org.ala.tiktools.mybatis.expand;

import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.ala.tiktools.mybatis.expand.binding.MapperMethodExpander;
import org.ala.tiktools.mybatis.interceptor.AnnoatationAndInterceptor;
import org.ala.tiktools.mybatis.interceptor.IAnnotationInterceptor;
import org.ala.tiktools.mybatis.interceptor.InterceptResult;
import org.ala.tiktools.mybatis.interceptor.SortInterceptor;
import org.ala.tiktools.tools.CachingKVTools;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.binding.MapperProxy;
import org.apache.ibatis.lang.UsesJava7;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;


/**
 * mapper代理扩展
 * - 提供注解Mapper，注解方法自定义逻辑
 *
 * @author ala
 * @date 2025-01-07 10:11
 */
public class MapperProxyExpander<T> extends MapperProxy<T> {

    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    protected MapperMethodHolder mapperMethodHolder;

    public MapperProxyExpander(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache, MapperMethodHolder methodHolder) {
        super(sqlSession, mapperInterface, methodCache);
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.mapperMethodHolder = methodHolder;
    }


    /**
     *  只有初始化时会给值，所以hashmap的安全的
     */
    protected Map<Class<? extends Annotation>, SortInterceptor> interceptorMap = new HashMap<>();
    /**
     *  method对应的注解拦截器
     */
    protected CachingKVTools<Method, List<AnnoatationAndInterceptor>> interceptors = new CachingKVTools<>(Collections.emptyList(), this::getInterceptors);


    /**
     * 真实执行mapper方法的地方
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //  expandDatas 会在多个intercept之间传递，用于共享数据
        Map<String, ExpandData> expandDatas = new HashMap<>();
        try {
            //  先跑一次cache，后面能拿到MapperMethod
            mapperMethodHolder.cachedMapperMethod(method.getDeclaringClass(), method);

            //  取method上绑定的interceptor
            List<AnnoatationAndInterceptor> interceptors = this.interceptors.get(method);
            if (interceptors == null || interceptors.isEmpty()) {
                return doInvoke(proxy, method, args, expandDatas);
            }

            //  执行interceptor
            for (AnnoatationAndInterceptor ai : interceptors) {
                InterceptResult v = ai.getInterceptor().intercept(ai.getAnno(), proxy, method, args, expandDatas);
                if (v.isReturnRes()) {
                    return v.getRes();
                }
            }
            return doInvoke(proxy, method, args, expandDatas);
        } finally {
        }
    }


    /**
     *  追加interceptor
     */
    public void appendInterceptor(IAnnotationInterceptor interceptor, int sort) {
        interceptorMap.put(interceptor.annotation(), SortInterceptor.build(interceptor, sort));
    }
    /**
     *  取method上的关键注解方法
     */
    protected List<AnnoatationAndInterceptor> getInterceptors(Method method) {
        Annotation[] annos = method.getAnnotations();
        List<AnnoatationAndInterceptor> list = new ArrayList<>();
        for (Annotation anno : annos) {
            SortInterceptor interceptor = interceptorMap.get(anno.annotationType());
            if (interceptor != null) {
                list.add(AnnoatationAndInterceptor.build(anno, interceptor.getInterceptor(), interceptor.getSort()));
            }
        }
        list.sort((i1, i2) -> i1.getSort() - i2.getSort());
        return list;
    }


    /**
     *  执行mybatis代理
     *  <p> 额外增加附加数据
     */
    public Object doInvoke(Object proxy, Method method, Object[] args, Map<String, ExpandData> appendData) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (isDefaultMethod(method)) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        final MapperMethodExpander mapperMethod = mapperMethodHolder.cachedMapperMethod(mapperInterface, method);
        return mapperMethod.execute(sqlSession, args, appendData);
    }
    private boolean isDefaultMethod(Method method) {
        return ((method.getModifiers()
                & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
                && method.getDeclaringClass().isInterface();
    }
    @UsesJava7
    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
            throws Throwable {
        final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                .getDeclaredConstructor(Class.class, int.class);
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }
        final Class<?> declaringClass = method.getDeclaringClass();
        return constructor
                .newInstance(declaringClass,
                        MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
                .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }

}
